﻿#region Copyright 2011 Mike Goatly
// This source is subject to the the MIT License (MIT)
// All rights reserved.
#endregion

namespace WebIndexerSample
{
    #region Using Statements
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Diagnostics;
    using System.Linq;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Navigation;
    using System.Windows.Threading;

    using Lifti;
    using Lifti.Persistence;
    using Lifti.Querying; 
    #endregion

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// The routed command for deleting an entry.
        /// </summary>
        public static readonly RoutedCommand DeleteEntryCommand = new RoutedCommand("DeleteEntry", typeof(MainWindow));

        /// <summary>
        /// The routed command for navigating to an entry.
        /// </summary>
        public static readonly RoutedCommand NavigateToEntryCommand = new RoutedCommand("NavigateToEntry", typeof(MainWindow));

        /// <summary>
        /// The event data.
        /// </summary>
        private readonly ObservableCollection<EventData> eventData = new ObservableCollection<EventData>();

        /// <summary>
        /// A stopwatch used to obtain timings in the sample application.
        /// </summary>
        private readonly Stopwatch watch = new Stopwatch();

        /// <summary>
        /// The persisted index.
        /// </summary>
        private PersistedFullTextIndex<string> index;

        /// <summary>
        /// Initializes a new instance of the <see cref="MainWindow"/> class.
        /// </summary>
        public MainWindow()
        {
            this.InitializeComponent();
            this.listLog.ItemsSource = this.eventData;

            this.btnGo.Click += this.Go_Click;
            this.btnSearch.Click += this.Search_Click;
            this.btnInvalidateRoot.Click += this.InvalidateRoot_Click;
            this.webBrowser.LoadCompleted += this.WebBrowser_LoadCompleted;

            var deleteBinding = new CommandBinding(DeleteEntryCommand, this.OnDeleteEntryExecuted);
            var navigateBinding = new CommandBinding(NavigateToEntryCommand, this.OnNavigateToEntryExecuted);
            this.CommandBindings.Add(deleteBinding);
            this.CommandBindings.Add(navigateBinding);

            this.LoadIndex();
            this.UpdateStats();

            this.HookExtensibilityMessages();
        }

        /// <summary>
        /// Creates the child stats for a set of data pages.
        /// </summary>
        /// <param name="dataPages">The data pages.</param>
        /// <returns>The stats for the data pages.</returns>
        private static IEnumerable<StatData> CreateChildStats(IDataPageCollection dataPages)
        {
            foreach (var page in dataPages)
            {
                yield return new StatData
                    {
                        Stat = "Page",
                        Data = page.PageNumber,
                        ChildStats = new[]
                            {
                                new StatData { Stat = "Entries", Data = page.EntryCount },
                                new StatData { Stat = "First entry id", Data = page.FirstEntry },
                                new StatData { Stat = "Last entry id", Data = page.LastEntry },
                                new StatData { Stat = "Current size", Data = page.CurrentSize },
                                new StatData { Stat = "% Full", Data = (float)page.CurrentSize / 8192F },
                            }
                    };
            }
        }

        /// <summary>
        /// Hooks the extensibility messages.
        /// </summary>
        private void HookExtensibilityMessages()
        {
            this.index.Extensibility.TransactionCommitted += (s, e) => this.AddLogEntry("Transaction committed");
            this.index.Extensibility.TransactionCommitting += (s, e) => this.AddLogEntry("Transaction committing");
            this.index.Extensibility.TransactionEnlisted += (s, e) => this.AddLogEntry("Transaction enlisted");
            this.index.Extensibility.TransactionPreparedForCommit += (s, e) => this.AddLogEntry("Transaction prepared for commit");
            this.index.Extensibility.TransactionPreparingForCommit += (s, e) => this.AddLogEntry("Transaction preparing for commit");
            this.index.Extensibility.TransactionRolledBack += (s, e) => this.AddLogEntry("Transaction rolled back: " + (e.Exception != null ? e.Exception.Message : String.Empty));
            this.index.Extensibility.TransactionRollingBack += (s, e) => this.AddLogEntry("Transaction rolling back: " + (e.Exception != null ? e.Exception.Message : String.Empty));
        }

        /// <summary>
        /// Called when the navigate to entry command is executed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void OnNavigateToEntryExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            this.webBrowser.Navigate((string)e.Parameter);
        }

        /// <summary>
        /// Loads the index.
        /// </summary>
        private void LoadIndex()
        {
            this.AddLogEntry("Beginning loading of index");
            this.watch.Restart();
            this.index = new PersistedFullTextIndex<string>("index.dat")
                {
                    WordSplitter = new XmlWordSplitter(new StemmingWordSplitter()),
                    SearchWordSplitter = new StemmingWordSplitter(),
                    QueryParser = new LiftiQueryParser()
                };

            this.AddLogEntry("Index loaded in " + this.watch.ElapsedMilliseconds + "ms");
        }

        /// <summary>
        /// Updates the displayed stats.
        /// </summary>
        private void UpdateStats()
        {
            var pageManager = this.index.PersistedEntryManager.PageManager;

            this.treeStats.Items.Clear();
            this.treeStats.Items.Add(new StatData { Stat = "Total pages", Data = pageManager.TotalPageCount });
            this.treeStats.Items.Add(new StatData { Stat = "Empty pages", Data = pageManager.UnusedPageCount });
            this.treeStats.Items.Add(new StatData { Stat = "Item pages", Data = pageManager.ItemDataPages.Count, ChildStats = CreateChildStats(pageManager.ItemDataPages).ToArray() });
            this.treeStats.Items.Add(new StatData { Stat = "Index node pages", Data = pageManager.IndexNodeDataPages.Count, ChildStats = CreateChildStats(pageManager.IndexNodeDataPages).ToArray() });
            this.treeStats.Items.Add(new StatData { Stat = "Item node index pages", Data = pageManager.ItemNodeIndexDataPages.Count, ChildStats = CreateChildStats(pageManager.ItemNodeIndexDataPages).ToArray() });
        }

        /// <summary>
        /// Called when the entry deleted command is executed.
        /// </summary>
        /// <param name="sender">
        /// The event sender.
        /// </param>
        /// <param name="e">
        /// The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.
        /// </param>
        private void OnDeleteEntryExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            var removeItem = (string)e.Parameter;
            this.AddLogEntry("Beginning removal of " + removeItem);
            this.watch.Restart();
            this.index.Remove(removeItem);
            this.AddLogEntry("Removal of " + removeItem + " completed in " + this.watch.ElapsedMilliseconds + "ms");

            var resultList = (List<string>)this.listResults.ItemsSource;
            resultList.Remove(removeItem);
            this.listResults.ItemsSource = null;
            this.listResults.ItemsSource = resultList;

            this.UpdateStats();
        }

        /// <summary>
        /// Adds a log entry..
        /// </summary>
        /// <param name="data">
        /// The data to log.
        /// </param>
        private void AddLogEntry(string data)
        {
            var newEntry = new EventData { Date = DateTime.Now, Data = data };

            // Oh, dirty dirty. Rollback prepare and commit messages come at us from other threads
            // Just invoking back onto the main thread will cause a deadlock, so run the task asynchronously
            // so that we can continue and free up the UI.
            Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(() => this.eventData.Insert(0, newEntry)));
        }

        /// <summary>
        /// Handles the Click event of the Search control.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.
        /// </param>
        private void Search_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                this.AddLogEntry(
                    "Beginning search: " +
                    this.index.QueryParser.ParseQuery(this.txtCriteria.Text, this.index.SearchWordSplitter));

                var stopwatch = new Stopwatch();
                stopwatch.Start();
                var results = this.index.Search(this.txtCriteria.Text).ToList();

                this.AddLogEntry("Search completed in " + stopwatch.ElapsedMilliseconds + "ms");

                this.listResults.ItemsSource = results;
            }
            catch (LiftiException ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        /// <summary>
        /// Handles the LoadCompleted event of the webBrowser control.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// The <see cref="System.Windows.Navigation.NavigationEventArgs"/> instance containing the event data.
        /// </param>
        private void WebBrowser_LoadCompleted(object sender, NavigationEventArgs e)
        {
            this.IndexCurrentPage();
        }

        /// <summary>
        /// Indexes the current page.
        /// </summary>
        private void IndexCurrentPage()
        {
            try
            {
                string url = this.webBrowser.Source.ToString();
                this.txtUrl.Text = url;

                this.watch.Restart();
                if (this.index.Contains(url))
                {
                    this.AddLogEntry("Beginning re-indexing of " + url);
                }
                else
                {
                    this.AddLogEntry("Beginning indexing of " + url);
                }

                this.index.Index(url, ((dynamic)this.webBrowser.Document).Body.InnerHtml);

                this.AddLogEntry("Indexing completed in " + this.watch.ElapsedMilliseconds + "ms");

                this.UpdateStats();
            }
            catch (LiftiException ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        /// <summary>
        /// Handles the Click event of the Go control.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.
        /// </param>
        private void Go_Click(object sender, RoutedEventArgs e)
        {
            if (this.txtUrl.Text.Length > 0)
            {
                var url = new Uri(this.txtUrl.Text.IndexOf("://") == -1 ? "http://" + this.txtUrl.Text : this.txtUrl.Text, UriKind.Absolute);
                this.txtUrl.Text = url.ToString();
                this.webBrowser.Navigate(url);
            }
        }

        /// <summary>
        /// Handles the Click event of the InvalidateRoot control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void InvalidateRoot_Click(object sender, RoutedEventArgs e)
        {
            ((PersistedIndexNode<string>)this.index.RootNode).Invalidate();
        }

        /// <summary>
        /// The event data.
        /// </summary>
        private class EventData
        {
            /// <summary>
            /// Gets or sets Date.
            /// </summary>
            public DateTime Date
            {
                get;
                set;
            }

            /// <summary>
            /// Gets or sets Data.
            /// </summary>
            public string Data
            {
                get;
                set;
            }
        }

        /// <summary>
        /// Information about a particular statistic.
        /// </summary>
        private class StatData
        {
            /// <summary>
            /// Gets or sets data associated to the stat.
            /// </summary>
            public float Data
            {
                get;
                set;
            }

            /// <summary>
            /// Gets or sets stat.
            /// </summary>
            public string Stat
            {
                get;
                set;
            }

            /// <summary>
            /// Gets or sets the child stats.
            /// </summary>
            /// <value>The child stats.</value>
            public StatData[] ChildStats
            {
                get;
                set;
            }
        }
    }
}
